package com.grm.common.aspect;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.grm.common.utils.AjaxUtils;
import com.grm.common.utils.DateUtils;
import com.grm.common.utils.JwtUtil;
import com.grm.common.annotation.Log;
import com.grm.sys.mapper.SysLogOperateMapper;
import com.grm.sys.model.SysLogOperate;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.*;

/**
 * desc: 日志切面类
 *
 * @author gaorimao
 */
@Aspect
@Component
@Slf4j
public class SysLogOperateAspect {
    @Autowired
    private SysLogOperateMapper sysLogOperateMapper;
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private JwtUtil jwtUtil;

    /**
     * desc: 自定义切点表达式，检测自定义注解@Log,凡是使用@Log的地方都会自动进入aop切面
     */
    @Pointcut("@annotation(com.grm.common.annotation.Log)")
    public void pointcut() {
    }

    /**
     * desc: 前置通知，记录controller层操作
     *
     * @param joinPoint 切入点
     */
    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint) {
        handleLog(joinPoint);
    }

    /**
     * desc: 通过反射获取日志信息，存入数据库
     *
     * @param joinPoint 切点
     */
    private void handleLog(final JoinPoint joinPoint) {
        // 获得注解
        Method method = getMethod(joinPoint);
        Log log = getAnnotationLog(method);
        if (log == null) {
            return;
        }
        SysLogOperate sysLogOperate = setSysLogInfo(joinPoint, method, log);
        sysLogOperateMapper.insert(sysLogOperate);
    }

    /**
     * desc: 初始化日志信息，存入数据库
     *
     * @return SysLog
     */
    private SysLogOperate setSysLogInfo(JoinPoint joinPoint, Method method, Log logAnnotation) {
        // 操作数据库日志表
        SysLogOperate sysLogOperate = new SysLogOperate();
        // 请求信息:0 ajax请求 1 非ajax请求
        sysLogOperate.setType(AjaxUtils.isAjax(request) ? 0 : 1);
        sysLogOperate.setAction(logAnnotation.value());
        sysLogOperate.setHost(request.getRemoteHost());
        sysLogOperate.setUri(request.getRequestURI());
        sysLogOperate.setHttpMethod(request.getMethod());
        String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        sysLogOperate.setClassMethod(classMethod);
        // 请求的方法参数值
        Object[] args = joinPoint.getArgs();
        // 请求的方法参数名称
        Class<?>[] paramTypes = method.getParameterTypes();
        String[] paramNames = new String[paramTypes.length];
        for (int i = 0; i < paramTypes.length; i++) {
            paramNames[i] = paramTypes[i].toString();
        }
        log.info("paramNames = {}", Arrays.toString(paramNames));

        if (args != null && paramNames.length > 0) {
            StringBuilder params = new StringBuilder();
            try {
                params = handleParams(params, args, Arrays.asList(paramNames));
            } catch (JsonProcessingException e) {
                log.error("[LogAspect] setSysLogInfo handleParams error", e);
            }
            sysLogOperate.setParams(params.toString());
        }
        //获取当前登陆人
        Long userId = jwtUtil.getUserId();
        sysLogOperate.setOperateId(userId);
        sysLogOperate.setOperateTime(new Date());
        return sysLogOperate;
    }

    /**
     * dsec: 是否存在注解，如果存在就获取
     */
    private Log getAnnotationLog(Method method) {
        if (method != null) {
            return method.getAnnotation(Log.class);
        }
        return null;
    }

    /**
     * desc: 通过反射或缺当前请求方法
     *
     * @param joinPoint 切点
     * @return Method
     */
    private Method getMethod(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature)signature;
        return methodSignature.getMethod();
    }

    /**
     * desc: 处理请求参数
     */
    private StringBuilder handleParams(StringBuilder params, Object[] args, List paramNames)
        throws JsonProcessingException {
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof Map) {
                Set set = ((Map)args[i]).keySet();
                List list = new ArrayList();
                List paramList = new ArrayList<>();
                for (Object key : set) {
                    list.add(((Map)args[i]).get(key));
                    paramList.add(key);
                }
                return handleParams(params, list.toArray(), paramList);
            } else {
                if (args[i] instanceof Serializable) {
                    Class<?> aClass = args[i].getClass();
                    try {
                        aClass.getDeclaredMethod("toString", new Class[] {null});
                        // 如果不抛出NoSuchMethodException 异常则存在 toString 方法 ，安全的writeValueAsString ，否则 走 Object的 toString方法
                        params.append("  ").append(paramNames.get(i)).append(": ")
                            .append(objectMapper.writeValueAsString(args[i]));
                    } catch (NoSuchMethodException e) {
                        params.append("  ").append(paramNames.get(i)).append(": ")
                            .append(objectMapper.writeValueAsString(args[i].toString()));
                    }
                } else if (args[i] instanceof MultipartFile file) {
                    params.append("  ").append(paramNames.get(i)).append(": ").append(file.getName());
                } else {
                    params.append("  ").append(paramNames.get(i)).append(": ").append(args[i]);
                }
            }
        }
        return params;
    }
}